﻿//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	RouterSource.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Smart.Common.Router
{
	/// <summary>
	/// Router source
	/// </summary>
	public abstract class RouterSource
	{
		/// <summary>
		/// Router context
		/// </summary>
		public RouterContext Ctx { get; set; }

		/// <summary>
		/// Load request values in context
		/// </summary>
		public virtual void LoadValuesInContext(object[] values)
		{
			Ctx.Values = values;
		}

		/// <summary>
		/// Get values key depends on values to store in cache info about request 
		/// </summary>
		public abstract object GetCacheValuesKey();

		protected object SyncLock;

		/// <summary>
		/// Router source
		/// </summary>
		protected RouterSource(RouterContext ctx)
		{
			Ctx = ctx;

			SyncLock = new object();
		}

		/// <summary>
		/// Templates tree cache
		/// </summary>
		protected virtual CacheEntry<RouterTemplateItem> GetTemplatesCache()
		{
			return
				new CacheEntry<RouterTemplateItem>(Const.Cache.RouterTemplatesTree)
					{
						WebDependent = false,
						UrlDependent = false,
						UserDependent = false,
						Timeout = SPServices.Cache.LongTimeout,
						SlidingExpiration = SPServices.Cache.DefaultTimeout,
						Priority = System.Web.Caching.CacheItemPriority.Low
					};
		}

		/// <summary>
		/// Loads and returns templates tree root
		/// </summary>
		protected virtual RouterTemplateItem GetTemplatesRoot()
		{
			var cacheEntry = GetTemplatesCache();

			var root = cacheEntry.Value;

			// Loaded from cache
			if (root != null)
				return root;

			lock (SyncLock)
			{
				root = cacheEntry.Value;

				// While waiting lock - someone already filled
				if (root != null)
					return root;

				cacheEntry.Value = root = new RouterTemplateItem();

				// Load settings only for rootWeb
				var routerDefinition = SPServices.Configuration.GetValue(SPH.GetSite(), SPH.GetRootWeb(), null, null, Const.Router.Web.ConfigSettingName, null);

				// No configuration defined - returns empty data
				if (routerDefinition.IsNullOrEmpty())
					return root;

				// build templates tree
				var routerTemplates = ParseSettings(routerDefinition);

				foreach (var routerTemplate in routerTemplates.Items)
				{
					var parts = routerTemplate.Name.Split('.');

					var current = root;

					for (int i = 0, iMax = parts.Length; i < iMax; i++)
					{
						var part = parts[i];

						var child = current.Children.SafeGet(part, (RouterTemplateItem)null);

						if (child == null)
							current.Children[part] = child = new RouterTemplateItem();

						current = child;
					}

					current.Templates.Add(routerTemplate);
				}
			}

			return root;
		}

		/// <summary>
		/// Return uri template by name
		/// </summary>
		public virtual RouterTemplate LoadRouterTemplate(string name)
		{
			var root = GetTemplatesRoot();

			var parts = name.Split('.');

			var current = root;

			RouterTemplateItem lastNonStrict = null;

			for (int i = 0, iMax = parts.Length; i < iMax; i++)
			{
				var part = parts[i];

				var child = current.Children.SafeGet(part, (RouterTemplateItem)null);

				if (child == null)
				{
					// No more children: return last non strict
					current = null;
					break;
				}

				foreach (var template in current.Templates)
				{
					if (template.Strict)
						continue;

					lastNonStrict = current;
					break;
				}
			}

			if (current != null)
			{
				RouterTemplate last = null;

				foreach (var template in current.Templates)
				{
					if (template.Strict)
						return template;

					last = template;
				}

				return last;
			}

			if (lastNonStrict != null)
			{
				foreach (var template in lastNonStrict.Templates)
				{
					if (!template.Strict)
						return template;
				}
			}

			throw new KeyNotFoundException("RouterTemplate \"" + name + "\" not found!");
		}

		/// <summary>
		/// Get the value of <paramref name="uriPart"/>. Returns <code>true</code> if <paramref name="partValue"/> replaces <paramref name="uriPart"/>
		/// </summary>
		public virtual bool GetUriPartValue(string uriPart, out string partValue)
		{
			partValue = uriPart;
			return false;
		}

		/// <summary>
		/// Add router template to the source if not exists
		/// </summary>
		/// <returns>True if router template doesn't exist and it was added. Otherwise false.</returns>
		public virtual bool AddRouterTemplate(RouterTemplate template)
		{
			try
			{
				lock (SyncLock)
				{
					var site = SPH.GetSite();
					var rootWeb = SPH.GetRootWeb();

					var routerDefinition = SPServices.Configuration.GetValue(site, rootWeb, null, null, Const.Router.Web.ConfigSettingName, null);

					var rts =
						routerDefinition.IsNullOrEmpty()
							? new RouterTemplates { Items = new RouterTemplate[0] }
							: ParseSettings(routerDefinition);

					var existingTemplate = Array.Find(rts.Items, routerTemplate => routerTemplate.Name.EqIC(template.Name));

					if (existingTemplate != null)
					{
						existingTemplate.Name = template.Name;
						existingTemplate.Strict = template.Strict;
						existingTemplate.Type = template.Type;
						existingTemplate.Uri = template.Uri;
					}
					else
					{
						rts.Items = rts.Items.Concat(new[] { template }).ToArray();
					}

					SPServices.Configuration.SetValue(rootWeb, Const.Router.Web.ConfigSettingName, FormatSettings(rts));

					var cache = GetTemplatesCache();

					cache.RemoveFromCache();
				}

				return true;
			}
			catch (Exception ex)
			{
				H.LX("RouterSource.AddRouterTemplate", ex);

				return false;
			}
		}

		/// <summary>
		/// Parse router settings
		/// </summary>
		protected virtual RouterTemplates ParseSettings(string routerDefinition)
		{
			using (var sr = new StringReader(routerDefinition))
			{
				return SerializationExts.DeserializeXml<RouterTemplates>(sr);
			}
		}

		/// <summary>
		/// Format router settings
		/// </summary>
		protected virtual string FormatSettings(RouterTemplates templates)
		{
			return SerializationExts.SerializeToXml(templates);
		}
	}
}